// SPDX-License-Identifier: GPL-2.0

/*
 * lombo_pdm.c - ALSA SoC Digital Audio Interface(DAI) driver
 * Common code for LomboTech SoCs
 *
 * Copyright (c) 2016-2018, LomboTech Co.Ltd. All rights reserved.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/module.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/pm_clock.h>
#include <linux/regmap.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include "linux/clk/lombo_clk.h"
#include "lombo_pdm.h"
#include "csp/n9v1/pdm/csp_pdm.h"
#include <mach/csp.h>
#include <mach/debug.h>

#undef  MOD_NAME
#define MOD_NAME "LOMBO_PDM"

#ifdef CONFIG_ARCH_LOMBO_N7V5
static int audiopll_rate[2] = {CLK_24576M_X8, CLK_225792M_X8};
#else
static int audiopll_rate[2] = {CLK_24576M_X6, CLK_225792M_X6};
#endif

struct lombo_pdm_dev {
	struct device   *dev;
	/*struct regmap   *regmap;*/
	void __iomem	*regs;
	struct clk	*pdm_clk;	/* pdm clk */
	struct clk	*parent_clk;
	struct clk	*pdm_apb_gate;
	struct clk	*pdm_apb_reset;
	unsigned int	pdm_osr;
	int			dat_input_num;
	int			dat_input_index[4];
	struct snd_dmaengine_dai_dma_data capture_dma_data;
};

/* define pcm dma params */
static const struct snd_pcm_hardware lombo_pcm_hardware = {
	.info			= SNDRV_PCM_INFO_INTERLEAVED |
	SNDRV_PCM_INFO_BLOCK_TRANSFER |
	SNDRV_PCM_INFO_MMAP |
	SNDRV_PCM_INFO_MMAP_VALID,
	/* max dma buffer bytes */
	.buffer_bytes_max	= DMA_BUFFER_MAX,
	/* min bytes for one period */
	.period_bytes_min	= PERIOD_BYTES_MIN,
	/* max bytes for one period */
	.period_bytes_max	= PERIOD_BYTES_MAX,
	.periods_min		= PERIOD_MIN,		/* min periods */
	.periods_max		= PERIOD_MAX,		/* max periods */
};

static int dma_slave_config(struct snd_pcm_substream *substream,
	struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_dmaengine_dai_dma_data *dma_data;
	int ret;

	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
	ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
	if (ret)
		return ret;
	dev_dbg(rtd->cpu_dai->dev, "dma addr %d burst %u\n",
		dma_data->addr_width, dma_data->maxburst);
	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
		slave_config->src_addr = dma_data->addr;
		slave_config->src_maxburst = dma_data->maxburst;
		slave_config->dst_maxburst = dma_data->maxburst;
	}

	if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED) {
		slave_config->dst_addr_width = dma_data->addr_width;
		slave_config->src_addr_width = dma_data->addr_width;
	}
	slave_config->slave_id = dma_data->slave_id;
	return 0;
}

/* for n7v3 we need a software highpass filter here */
#if defined(CONFIG_ARCH_LOMBO_N7V3) || defined(CONFIG_ARCH_LOMBO_N9V1)
static short staurate16(int x)
{
	int sign;

	/* clip to [-32768, 32767] */
	sign = x >> 31;
	if (sign != (x >> 15))
		x = sign ^ ((1 << 15) - 1);

	return (short)x;

}

static int mulshift32(int x, int y)
{
	int z;

	z = (int64_t)x * (int64_t)y >> 32;

	return z;
}

static int highpassfixed(unsigned char *ptr, int len)
{
	static int y1;
	static int x1;

	int a = 2147343438; /* Q31 float a = 0.9999347096405159; */
	int b1 = 2147203228; /* Q31 float b1 = 0.99986941928103168; */
	short *pin = (short *)ptr;
	int i = 0;
	int samples = len >> 1;
	int temp = 0;
	int out = 0;

	for (i = 0; i < samples; i++) {
		temp = (int)pin[i] << 15;
		out = mulshift32(a, (temp - x1)) + mulshift32(b1, y1);
		pin[i] = staurate16(out >> 14);
		x1 = temp;
		y1 = out << 1;
	}

	return 0;
}

static int hipass_process(struct snd_pcm_substream *substream,
		       int channel, unsigned long hwoff,
		       void *buf, unsigned long bytes)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	void *dma_ptr = runtime->dma_area + hwoff +
			channel * (runtime->dma_bytes / runtime->channels);

	return highpassfixed(dma_ptr, bytes);
}

static const struct snd_dmaengine_pcm_config lombo_pdm_dmaengine_pcm_config = {
	.prepare_slave_config	= dma_slave_config,
	.prealloc_buffer_size	= PREALLOC_PCM_BUFFER,
	.pcm_hardware		= &lombo_pcm_hardware,
	.process		= hipass_process,
};
#else
static const struct snd_dmaengine_pcm_config lombo_pdm_dmaengine_pcm_config = {
	.prepare_slave_config	= dma_slave_config,
	.prealloc_buffer_size	= PREALLOC_PCM_BUFFER,
	.pcm_hardware		= &lombo_pcm_hardware,
};
#endif

struct lombo_pdm_clkref {
	int rate;
	int clk_div;
};

/*
 *we use osr is 64 here, so:
 * clk_div = pll_clk / (osr * sample_rate)
 */
static struct lombo_pdm_clkref clkdiv_table[] = {
	{8000,   48},
	{16000,  24},
	{24000,  16},
	{32000,  12},
	{48000,  8},
	{96000,  4},
	{192000,  2},
	{11025,  32},
	{22050,  16},
	{44100,  8},
	{88200,  4},
};

static unsigned int get_pdm_clkdiv(unsigned int sr, unsigned int osr)
{
	unsigned int i, count, div;

	div = 0;
	if (!sr)
		return div;

	count = ARRAY_SIZE(clkdiv_table);
	for (i = 0; i < count; i++) {
		if (sr == clkdiv_table[i].rate) {
			if (osr == 32)
				div = clkdiv_table[i].clk_div << 1;
			else
				div = clkdiv_table[i].clk_div;
			break;
		}
	}
	return div;
}

static inline struct lombo_pdm_dev *to_info(struct snd_soc_dai *dai)
{
	return snd_soc_dai_get_drvdata(dai);
}

static void lombo_pdm_rxctrl(struct lombo_pdm_dev *pdm, int on)
{
	if (on)
		csp_pdm_rx_open(pdm->regs);
	else
		csp_pdm_rx_close(pdm->regs);
}

static int lombo_pdm_hw_params(struct snd_pcm_substream *substream,
	struct snd_pcm_hw_params *params,
	struct snd_soc_dai *dai)
{
	struct lombo_pdm_dev *pdm = to_info(dai);
	struct snd_dmaengine_dai_dma_data *dma_data;
	unsigned int clk_div, samplerate;
	int index = 0;
	reg_pdm_t *reg_pdm = pdm->regs;

	dma_data = snd_soc_dai_get_dma_data(dai, substream);

	samplerate = params_rate(params);

	dev_dbg(pdm->dev, "set osr %u\n", pdm->pdm_osr);
	/* set pdm clock div */
	clk_div = get_pdm_clkdiv(samplerate, pdm->pdm_osr);
	if (!clk_div)
		return -EINVAL;
	dev_dbg(pdm->dev, "set clk_div %u\n", clk_div);
	csp_pdm_set_div(pdm->regs, clk_div - 1);
	dev_dbg(pdm->dev, "div [0x10]:%08x\n", READREG32(&reg_pdm->ctrl));

	/* set pdm osr */
	csp_pdm_set_osr(pdm->regs, pdm->pdm_osr);
	dev_dbg(pdm->dev, "osr [0x10]:%08x\n", READREG32(&reg_pdm->ctrl));

	dev_dbg(pdm->dev, "set samplerate %u\n", samplerate);
	/* set pdm sample rate */
	csp_pdm_set_fs(pdm->regs, samplerate);
	dev_dbg(pdm->dev, "fs [0x10]:%08x\n", READREG32(&reg_pdm->ctrl));

	dev_dbg(pdm->dev, "set format %u\n", params_format(params));
	/* set pdm bit width */
	switch (params_format(params)) {
	case SNDRV_PCM_FORMAT_S16_LE:
		csp_pdm_set_wlen(pdm->regs, 16);
		dma_data->addr_width = 2;
		break;
	case SNDRV_PCM_FORMAT_S32_LE:
		csp_pdm_set_wlen(pdm->regs, 32);
		dma_data->addr_width = 4;
		break;
	default:
		dev_err(pdm->dev, "invalid format: %d\n",
			params_format(params));
		return -EINVAL;
	}
	dev_dbg(pdm->dev, "bw [0x50]:%08x\n", READREG32(&reg_pdm->rfcr));

	dev_dbg(pdm->dev, "set channel %u\n", params_channels(params));
	/* set pdm channel */
	switch (params_channels(params)) {
	case 1:
		csp_pdm_set_channel(pdm->regs, 1);
		index |= 1 << (pdm->dat_input_index[0] - 1);
		break;
	case 2:
		index |= 1 << (pdm->dat_input_index[0] - 1);
		csp_pdm_set_channel(pdm->regs, 2);
		break;
	case 4:
		if (pdm->dat_input_num != 2) {
			dev_err(pdm->dev, "invalid channel:%d, dat_num not enough\n",
				params_channels(params));
			return -EINVAL;
		}
		index |= 1 << (pdm->dat_input_index[0] - 1);
		index |= 1 << (pdm->dat_input_index[1] - 1);
		csp_pdm_set_channel(pdm->regs, 4);
		break;
	case 8:
		if (pdm->dat_input_num != 4) {
			dev_err(pdm->dev, "invalid channel:%d, dat_num not enough\n",
				params_channels(params));
			return -EINVAL;
		}
		index |= 1 << (pdm->dat_input_index[0] - 1);
		index |= 1 << (pdm->dat_input_index[1] - 1);
		index |= 1 << (pdm->dat_input_index[2] - 1);
		index |= 1 << (pdm->dat_input_index[3] - 1);
		csp_pdm_set_channel(pdm->regs, 8);
		break;
	default:
		dev_err(pdm->dev, "invalid channel: %d\n",
			params_channels(params));
		return -EINVAL;
	}
	csp_pdm_set_dat_enable(pdm->regs, index);

	/* enable pdm module */
	csp_pdm_enable(pdm->regs);
	dev_dbg(pdm->dev, "[0x10]:%08x\n", READREG32(&reg_pdm->ctrl));
	/* enable pdm rx before dma */
	lombo_pdm_rxctrl(pdm, 1);
	mdelay(100);

	return 0;
}

static int lombo_pdm_trigger(struct snd_pcm_substream *substream, int cmd,
	struct snd_soc_dai *dai)
{
	struct lombo_pdm_dev *pdm = to_info(dai);
	int ret = 0;

	dev_dbg(pdm->dev, "pdm trigger %d\n", cmd);
	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		pm_runtime_get_sync(dai->dev);
		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
			lombo_pdm_rxctrl(pdm, 1);
		break;
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
			lombo_pdm_rxctrl(pdm, 0);
		pm_runtime_put(dai->dev);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

/* set the pdm clk and clk rate */
static int lombo_pdm_set_sysclk(struct snd_soc_dai *dai,
	int clk_id, unsigned int freq, int dir)
{
	int ret = 0;
	struct lombo_pdm_dev *pdm = to_info(dai);

	dev_dbg(pdm->dev, "clkid %d, rate %d\n", clk_id, freq);
	/* set rate for audio pll clk */
	ret = clk_set_rate(pdm->parent_clk, audiopll_rate[clk_id]);
	if (ret)
		dev_err(pdm->dev, "Fail to set clk parent %d\n", ret);
	ret = clk_set_rate(pdm->pdm_clk, freq);
	if (ret)
		dev_err(pdm->dev, "Fail to set clk %d\n", ret);

	return ret;
}

static int lombo_pdm_dai_probe(struct snd_soc_dai *dai)
{
	struct lombo_pdm_dev *pdm = to_info(dai);

	pm_runtime_get_sync(dai->dev);

	dai->capture_dma_data = &pdm->capture_dma_data;
	dev_dbg(pdm->dev, "pdm enbale\n");

	/* enable HPF  */
	csp_pdm_hpf_enable(pdm->regs);

	pm_runtime_put(dai->dev);
	return 0;
}

static int lombo_pdm_dai_remove(struct snd_soc_dai *dai)
{
	struct lombo_pdm_dev *pdm = to_info(dai);

	pm_runtime_get_sync(dai->dev);

	/* disable pdm module */
	csp_pdm_disable(pdm->regs);

	/* disable HPF  */
	csp_pdm_hpf_disable(pdm->regs);

	pm_runtime_put(dai->dev);
	return 0;
}

static const struct snd_soc_dai_ops lombo_pdm_dai_ops = {
	.trigger     = lombo_pdm_trigger,
	.hw_params   = lombo_pdm_hw_params,
	.set_sysclk  = lombo_pdm_set_sysclk,
};

#define LOMBO_PDM_RATES SNDRV_PCM_RATE_8000_192000
#define LOMBO_PDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
			      SNDRV_PCM_FMTBIT_S32_LE)

static struct snd_soc_dai_driver lombo_pdm_dai = {
	.name = "lombo-pdm",
	.probe = lombo_pdm_dai_probe,
	.remove = lombo_pdm_dai_remove,
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 8,
		.rates = LOMBO_PDM_RATES,
		.formats = LOMBO_PDM_FORMATS,
	},
	.ops = &lombo_pdm_dai_ops,
	.symmetric_rates = 1,
};

static const struct snd_soc_component_driver lombo_pdm_component = {
	.name = "lombo_pdm",
};

/*
 *static bool lombo_pdm_wr_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case PDM_CTRL:
	case PDM_CHAN:
	case PDM_START:
	case PDM_RXFFR:
	case PDM_IE:
	case PDM_ICR:
	case PDM_RFCR:
		return true;
	default:
		return false;
	}
}
*/

/*
 *static bool lombo_pdm_rd_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case PDM_CTRL:
	case PDM_CHAN:
	case PDM_START:
	case PDM_ISR:
	case PDM_IE:
	case PDM_RFCR:
	case PDM_RXDMA:
	case PDM_MAX_REG:
		return true;
	default:
		return false;
	}
}
*/

/*
 *static bool lombo_pdm_volatile_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case PDM_CTRL:
	case PDM_ICR:
	case PDM_ISR:
		return true;
	default:
		return false;
	}
}
*/

/*
 *static const struct regmap_config lombo_pdm_regmap_config = {
	.reg_bits = 32,
	.reg_stride = 4,
	.val_bits = 32,
	.max_register = PDM_MAX_REG,
	.writeable_reg = lombo_pdm_wr_reg,
	.readable_reg = lombo_pdm_rd_reg,
	.volatile_reg = lombo_pdm_volatile_reg,
	.cache_type = REGCACHE_FLAT,
};
*/

static int lombo_pdm_probe(struct platform_device *pdev)
{
	struct lombo_pdm_dev *pdm;
	struct resource *res;
	void __iomem *regs;
	int ret;

	pdm = devm_kzalloc(&pdev->dev, sizeof(*pdm), GFP_KERNEL);
	if (!pdm)
		return -ENOMEM;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	regs = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(regs))
		return PTR_ERR(regs);

	/* To do, add the regmap for pdm register */
	/*
	 * pdm->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
				&lombo_pdm_regmap_config);
		if (IS_ERR(pdm->regmap))
			return PTR_ERR(pdm->regmap);
	*/
	pdm->regs = regs;
	pdm->capture_dma_data.addr = csp_pdm_dmaaddr_in();
	pdm->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
	pdm->capture_dma_data.maxburst = LOMBO_PDM_DMA_BURST_SIZE;
	pdm->dev = &pdev->dev;
	/*
	 * dev_dbg(pdm->dev, "get pdm capture dma address %llu\n",
		pdm->capture_dma_data.addr);
	*/
	dev_set_drvdata(&pdev->dev, pdm);

	/* get and enable apb gate */
	pdm->pdm_apb_gate = devm_clk_get(&pdev->dev, "pdm_apb_gate");
	if (IS_ERR(pdm->pdm_apb_gate)) {
		dev_err(&pdev->dev, "get pdm apb gate error\n");
		return PTR_ERR(pdm->pdm_apb_gate);
	}
#ifdef CONFIG_PM_GENERIC_DOMAINS
	ret = pm_clk_create(&pdev->dev);
	ret |= pm_clk_add_clk(&pdev->dev, pdm->pdm_apb_gate);
#else
	ret = clk_prepare_enable(pdm->pdm_apb_gate);
#endif
	if (ret) {
		dev_err(&pdev->dev, "enable pdm apb gate error\n");
		return ret;
	}

	/* get and enable apb reset */
	pdm->pdm_apb_reset = devm_clk_get(&pdev->dev, "pdm_apb_reset");
	if (IS_ERR(pdm->pdm_apb_reset)) {
		dev_err(&pdev->dev, "get pdm apb reset error\n");
		return PTR_ERR(pdm->pdm_apb_reset);
	}
	ret = clk_prepare_enable(pdm->pdm_apb_reset);
	if (ret) {
		dev_err(&pdev->dev, "enable pdm apb gate error\n");
		return ret;
	}

	/* get pdm clock and enable it */
	pdm->pdm_clk = devm_clk_get(&pdev->dev, "pdm_module_clk");
	if (IS_ERR(pdm->pdm_clk)) {
		dev_err(&pdev->dev, "get pdm apb reset error\n");
		return PTR_ERR(pdm->pdm_clk);
	}

	pdm->parent_clk = clk_get(NULL, "audio_pll");
	if (IS_ERR(pdm->parent_clk)) {
		dev_err(&pdev->dev, "get audio pll error\n");
		return PTR_ERR(pdm->parent_clk);
	}

	clk_pll_set_mode(pdm->parent_clk, PLL_MODE_FRAC);
	ret = clk_set_rate(pdm->parent_clk, audiopll_rate[0]);
	if (ret)
		dev_err(&pdev->dev, "Fail to set clk parent %d\n", ret);

	ret = clk_set_parent(pdm->pdm_clk, pdm->parent_clk);
	if (ret)
		dev_err(&pdev->dev, "Fail to set clk parent %d\n", ret);

	/* set default clock rate CLK_24576M */
	clk_set_rate(pdm->pdm_clk, CLK_24576M);
#ifdef CONFIG_PM_GENERIC_DOMAINS
	ret = pm_clk_add_clk(&pdev->dev, pdm->pdm_clk);
	ret |= pm_clk_resume(&pdev->dev);
#else
	ret = clk_prepare_enable(pdm->pdm_clk);
#endif
	if (ret) {
		dev_err(&pdev->dev, "enable pdm apb clock error\n");
		return ret;
	}
#ifdef CONFIG_PM_GENERIC_DOMAINS
	pm_runtime_set_autosuspend_delay(&pdev->dev, 100);
	pm_runtime_use_autosuspend(&pdev->dev);
	/* set pm_runtime active */
	pm_runtime_set_active(&pdev->dev);
	/* enable pm_runtime */
	pm_runtime_enable(&pdev->dev);
#endif

	/* Get PDM OSR */
	ret = device_property_read_u32(&pdev->dev, "pdm-osr",
			&pdm->pdm_osr);
	if (ret) {
		dev_err(&pdev->dev, "get pdm osr error\n");
		ret = -EINVAL;
		goto err_clk;
	}
	dev_dbg(pdm->dev, "get pdm osr %d\n", pdm->pdm_osr);
	/* pdm only support osr's value is 32 or 64, other value is error.*/
	if (pdm->pdm_osr != 32 && pdm->pdm_osr != 64) {
		dev_err(&pdev->dev, "pdm osr %d is not correted\n",
			pdm->pdm_osr);
		ret = -EINVAL;
		goto err_clk;
	}

	ret = device_property_read_u32(&pdev->dev, "dat_input_num",
			&pdm->dat_input_num);
	if (ret) {
		dev_err(&pdev->dev, "get dat_input_num error\n");
		ret = -EINVAL;
		goto err_clk;
	}
	dev_dbg(pdm->dev, "get pdm data input num %d\n",
		pdm->dat_input_num);
	/*only support One/Two/Four Data input number. */
	if (pdm->dat_input_num == 3 || pdm->dat_input_num > 4) {
		dev_err(&pdev->dev, "dat_input_num %d is not correted\n",
			pdm->dat_input_num);
		ret = -EINVAL;
		goto err_clk;
	}

	ret = device_property_read_u32_array(&pdev->dev, "dat_input_index",
			pdm->dat_input_index, pdm->dat_input_num);
	if (ret) {
		dev_err(&pdev->dev, "get dat_input_index error\n");
		ret = -EINVAL;
		goto err_clk;
	}

	ret = devm_snd_soc_register_component(&pdev->dev,
			&lombo_pdm_component,
			&lombo_pdm_dai, 1);
	if (ret) {
		dev_err(&pdev->dev, "could not register dai: %d\n", ret);
		goto err_clk;
	}

	ret = devm_snd_dmaengine_pcm_register(&pdev->dev,
			&lombo_pdm_dmaengine_pcm_config,
			SND_DMAENGINE_PCM_FLAG_COMPAT);
	if (ret) {
		dev_err(&pdev->dev, "could not register pcm: %d\n", ret);
		goto err_clk;
	}
	dev_dbg(pdm->dev, "pdm register is ok!\n");
	return 0;

err_clk:
	if (pdm->pdm_apb_reset)
		clk_disable_unprepare(pdm->pdm_apb_reset);
#ifdef CONFIG_PM_GENERIC_DOMAINS
	pm_runtime_suspend(&pdev->dev);
	pm_runtime_disable(&pdev->dev);
#else
	if (pdm->pdm_clk)
		clk_disable_unprepare(pdm->pdm_clk);
	if (pdm->pdm_apb_gate)
		clk_disable_unprepare(pdm->pdm_apb_gate);
#endif

	return ret;
}

static int lombo_pdm_remove(struct platform_device *pdev)
{
	struct lombo_pdm_dev *pdm = dev_get_drvdata(&pdev->dev);

#ifdef CONFIG_PM_GENERIC_DOMAINS
	pm_runtime_put_sync(&pdev->dev);
	pm_runtime_disable(&pdev->dev);
	pm_clk_remove_clk(&pdev->dev, pdm->pdm_apb_gate);
	pm_clk_remove_clk(&pdev->dev, pdm->pdm_clk);
#else
	clk_disable_unprepare(pdm->pdm_apb_gate);
	clk_disable_unprepare(pdm->pdm_clk);
#endif
	clk_disable_unprepare(pdm->pdm_apb_reset);

	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int  pm_pdm_suspend(struct device *dev)
{
	struct lombo_pdm_dev *pdm = dev_get_drvdata(dev);

	PRT_INFO("====enter=====\n");
	clk_disable_unprepare(pdm->pdm_apb_reset);
#ifndef CONFIG_PM_GENERIC_DOMAINS
	clk_disable_unprepare(pdm->pdm_apb_gate);
	clk_disable_unprepare(pdm->pdm_clk);
#endif

	return 0;
}

static int  pm_pdm_resume(struct device *dev)
{
	struct lombo_pdm_dev *pdm = dev_get_drvdata(dev);

	PRT_INFO("====enter=====\n");
	clk_prepare_enable(pdm->pdm_apb_reset);

#ifndef CONFIG_PM_GENERIC_DOMAINS
	clk_prepare_enable(pdm->pdm_apb_gate);
	clk_prepare_enable(pdm->pdm_clk);
	csp_pdm_hpf_enable(pdm->regs);
#else
	if (pm_runtime_status_suspended(dev))
		pm_clk_resume(dev);
	csp_pdm_hpf_enable(pdm->regs);
	if (pm_runtime_status_suspended(dev))
		pm_clk_suspend(dev);
#endif

	return 0;
}

#endif

#ifdef CONFIG_PM
static const struct dev_pm_ops lombo_pdm_dev_pm_ops = {
#ifdef CONFIG_PM_SLEEP
	SET_SYSTEM_SLEEP_PM_OPS(pm_pdm_suspend,
		pm_pdm_resume)
#endif
};

#define LOMBO_DEV_PM_OPS (&lombo_pdm_dev_pm_ops)
#else
#define LOMBO_DEV_PM_OPS NULL
#endif

static const struct of_device_id lombo_pdm_match[] = {
	{ .compatible = "lombo, pdm", },
	{},
};
MODULE_DEVICE_TABLE(of, lombo_pdm_match);

static struct platform_driver lombo_pdm_driver = {
	.probe  = lombo_pdm_probe,
	.remove = lombo_pdm_remove,
	.driver = {
		.name = "lombo-pdm",
		.of_match_table = of_match_ptr(lombo_pdm_match),
		.pm = LOMBO_DEV_PM_OPS,
	},
};

module_platform_driver(lombo_pdm_driver);


/* Module information */
MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("Lombo PDM Controller Driver");
MODULE_LICENSE("GPL v2");

